package core.domain;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class IOUtil {
	//TODO: move this class to the core package, as it is not a domain class!
	private Class<? extends Serializable> domainClass;

	private Unmarshaller unmarsaller;
	private Marshaller marsaller;

	private <T extends Class<? extends Serializable>> IOUtil(T clazz) {
		domainClass = clazz;
		JAXBContext xmlTransformerContext;
		try {
			xmlTransformerContext = JAXBContext.newInstance(domainClass);
			unmarsaller = xmlTransformerContext.createUnmarshaller();
			marsaller = xmlTransformerContext.createMarshaller();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
	}

	public static <T extends Class<? extends Serializable>> IOUtil getInstance(T clazz) {
		return new IOUtil(clazz);
	}

	public <T extends Serializable> String objectToString(T objectToMarshal) {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		try {
			marsaller.marshal(objectToMarshal, output);
			return output.toString();
		} catch (JAXBException e) {
			e.printStackTrace();
			throw new RuntimeException("Problem while marshaling Object to String", e.getCause());
		}
	}

	public <T extends Serializable> T stringToObject(String stringToUnmarshal) {
		ByteArrayInputStream input = new ByteArrayInputStream(stringToUnmarshal.getBytes());
		try {
			return (T) unmarsaller.unmarshal(input);
		} catch (JAXBException e) {
			e.printStackTrace();
			throw new RuntimeException("Problem while unmarshaling String to Object");
		}
	}

	public static void writeMessageToSocket(Socket clientSocket, String outgoingMessage, boolean terminate) {
		synchronized (clientSocket) {
			try {
				PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
				out.println(outgoingMessage);
				clientSocket.shutdownOutput();
				if (terminate) {
					out.close();
					clientSocket.close();// closes streams as well!
				}
			} catch (IOException e) {
				e.printStackTrace();
				throw new RuntimeException("could not read from socket!", e.getCause());
			}
		}
	}

	public static String readMessageFromSocket(Socket clientSocket, boolean terminate) {
		synchronized (clientSocket) {
			try {
				StringBuffer result = new StringBuffer();
				BufferedReader inputBuffer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

				for (String line = new String(); line != null; line = inputBuffer.readLine()) {
					result.append(line);
				}
				if (terminate) {
					inputBuffer.close();
					clientSocket.close();
				}
				return result.toString();
			} catch (IOException e) {
				e.printStackTrace();
				throw new RuntimeException("could not read from socket!", e.getCause());
			}
		}
	}

	public static synchronized PeerDesciptor socketToPeerDescriptor(Socket clientSocket) {
		PeerDesciptor result = new PeerDesciptor(clientSocket.getInetAddress().getAddress(), clientSocket.getPort());
		return result;
	}

	public static synchronized Socket peerDescriptorToSocket(PeerDesciptor peer) {
		try {
			return new Socket(InetAddress.getByAddress(peer.getIpAddress()), peer.getPort());
		} catch (UnknownHostException e) {
			e.printStackTrace();
			throw new RuntimeException("peer to socket conversion", e.getCause());
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("peer to socket conversion", e.getCause());
		}
	}

	public static synchronized File writeDataOut(List<Part> completeListOfDataPieces, File outputFile) {
		try {
			int i = 0;
			while(outputFile.exists()){
				outputFile.renameTo(new File(outputFile.getAbsolutePath(), outputFile.getName()+""+i));
			}
			System.out.println("	writing out to:" + outputFile.getAbsoluteFile());
			PrintWriter pw = new PrintWriter(outputFile);
			for (Part part : completeListOfDataPieces) {
				for (String row : part.getPayloadRows()) {
					pw.print(row);
				}
			}
			pw.flush();
			pw.close();
			return outputFile;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException("couldn't write out data file!", e.getCause());
		}
	}

	/**
	 * if this file was empty propably it is not working expectedly!
	 * 
	 * @param fileName
	 * @return
	 */
	public static synchronized List<Part> createParts(String fileName) {
		List<Part> result = new ArrayList<Part>();
		try {

			File file = new File(fileName);
			BufferedReader br = new BufferedReader(new FileReader(file));
			String row = "";
			//FIXME: if this file was empty propably it is not working expectedly!
			List<String> payload = new ArrayList<String>();
			while (row != null) {
				if ((row = br.readLine()) == null || payload.size() == 500) {
					result.add(new Part(result.size(), payload));
					payload = new ArrayList<String>();
				}
				payload.add(row);
			}
			Torrent torrent = new Torrent(fileName, result);
			return result;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException("Problem while creating parts from file", e.getCause());
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("Problem while creating parts from file", e.getCause());
		}
	}
}
